Skip to content

refactor (backup) : conditionally copy registry auth secret from operator namespace to workspace namespace for backup/restore#1618

Draft
rohanKanojia wants to merge 1 commit intodevfile:mainfrom
rohankanojia-forks:pr/secret-fallback-option
Draft

refactor (backup) : conditionally copy registry auth secret from operator namespace to workspace namespace for backup/restore#1618
rohanKanojia wants to merge 1 commit intodevfile:mainfrom
rohankanojia-forks:pr/secret-fallback-option

Conversation

@rohanKanojia
Copy link
Copy Markdown
Member

@rohanKanojia rohanKanojia commented May 5, 2026

What does this PR do?

Only copy the registry auth secret from the operator namespace to the workspace namespace when it is configured inside DevWorkspaceOperatorConfig. Always give precedence to the user-defined secret named devworkspace-backup-registry-auth in the workspace namespace; do not modify it when it's present while copying.

What issues does this PR fix or reference?

https://redhat.atlassian.net/browse/CRW-10758

Is it tested? How?

Scenario 1: AuthSecret Not Configured (Secure by Default)

Expected: Operator will NOT copy secrets. User must provide their own.

  1. Configure DWOC without AuthSecret:
kubectl apply -f - <<EOF
apiVersion: controller.devfile.io/v1alpha1
kind: DevWorkspaceOperatorConfig
metadata:
  name: devworkspace-operator-config
  namespace: openshift-operators
config:
  workspace:
    backupCronJob:
      enable: true
      schedule: "* * * * *"
      registry:
        path: "quay.io/rokumar"
        # authSecret is NOT set - secure by default
EOF
  1. Create test namespace
oc new-project test-workspace-no-auth
  1. Create a DevWorkspace
kubectl apply -f - <<EOF
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: test-workspace-no-copy
  namespace: test-workspace-no-auth
spec:
  started: true
  template:
    projects:
      - name: web-nodejs-sample
        git:
          remotes:
            origin: "https://github.com/che-samples/web-nodejs-sample.git"
    components:
      - name: dev
        container:
          image: quay.io/devfile/universal-developer-image:latest
          memoryLimit: 512Mi
EOF
  1. Wait for workspace to become running, then stop it:
kubectl patch devworkspace test-workspace-no-copy -n test-workspace-no-auth \
  --type merge -p '{"spec":{"started":false}}'
  1. Expected Result: Check operator logs - should show error about missing AuthSecret:
oc logs -l app.kubernetes.io/name=devworkspace-controller -n openshift-operators -f

Expected log:

{"level":"error","ts":"2026-05-08T13:15:00Z","logger":"controllers.BackupCronJob","msg":"Failed to create backup Job for DevWorkspace","id":"workspacef888cb9f6e1a448b","error":"registry auth secret \"devworkspace-backup-registry-auth\" not found in workspace namespace \"test-workspace-no-auth\" and AuthSecret is not configured in DevWorkspaceOperatorConfig. Either create the secret in the workspace namespace or configure Registry.AuthSecret in the operator config to enable copying from the operator namespace","stacktrace":"github.com/devfile/devworkspace-operator/controllers/backupcronjob.(*BackupCronJobReconciler).executeBackupSync\n\t/devworkspace-operator/controllers/backupcronjob/backupcronjob_controller.go:254\ngithub.com/devfile/devworkspace-operator/controllers/backupcronjob.(*BackupCronJobReconciler).startCron.func1\n\t/devworkspace-operator/controllers/backupcronjob/backupcronjob_controller.go:194\ngithub.com/robfig/cron/v3.FuncJob.Run\n\t/devworkspace-operator/vendor/github.com/robfig/cron/v3/cron.go:131\ngithub.com/robfig/cron/v3.(*Cron).startJob.func1\n\t/devworkspace-operator/vendor/github.com/robfig/cron/v3/cron.go:307"}
  1. Verify no secret was created:
kubectl get secret devworkspace-backup-registry-auth -n test-workspace-no-auth
# Expected: Error from server (NotFound)
Scenario 2: AuthSecret Configured - Operator Copies Secret

Expected: Operator copies secret from operator namespace to workspace namespace.

  1. Create operator secret first:
kubectl create secret docker-registry quay-push-secret \
  --docker-server=quay.io \
  --docker-username=<your-username> \
  --docker-password=<your-token> \
  -n openshift-operators

kubectl label secret quay-push-secret [controller.devfile.io/watch-secret=true](http://controller.devfile.io/watch-secret=true) -n openshift-operators
  1. Configure DWOC with AuthSecret:
kubectl apply -f - <<EOF
apiVersion: controller.devfile.io/v1alpha1
kind: DevWorkspaceOperatorConfig
metadata:
  name: devworkspace-operator-config
  namespace: openshift-operators
config:
  workspace:
    backupCronJob:
      enable: true
      schedule: "* * * * *"
      registry:
        path: "quay.io/rokumar"
        authSecret: "quay-push-secret"  # Configured - will enable copying
EOF
  1. Create test namespace
oc new-project test-workspace-copy
  1. Create a DevWorkspace
kubectl apply -f - <<EOF
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: test-workspace-with-copy
  namespace: test-workspace-copy
spec:
  started: true
  template:
    projects:
      - name: web-nodejs-sample
        git:
          remotes:
            origin: "https://github.com/che-samples/web-nodejs-sample.git"
    components:
      - name: dev
        container:
          image: quay.io/devfile/universal-developer-image:latest
EOF
  1. Wait for workspace to become running, then stop it:
kubectl patch devworkspace test-workspace-with-copy -n test-workspace-copy \
  --type merge -p '{"spec":{"started":false}}'
  1. Expected Result: Verify secret was copied automatically:
kubectl get secret devworkspace-backup-registry-auth -n test-workspace-copy
# Expected: ✅ Secret exists

# Verify it has the controller.devfile.io/watch-secret label
kubectl get secret devworkspace-backup-registry-auth -n test-workspace-copy -o jsonpath='{.metadata.labels}'
Scenario 3: User-Provided Secret NEVER gets Overwritten

Expected: User's secret is respected and NEVER overwritten, even if operator has different credentials.

  1. Create operator secret:
kubectl create secret docker-registry quay-push-secret \
  --docker-server=quay.io \
  --docker-username=operator-user \
  --docker-password=operator-token \
  -n openshift-operators

kubectl label secret quay-push-secret [controller.devfile.io/watch-secret=true](http://controller.devfile.io/watch-secret=true) -n openshift-operators
  1. Configure DWOC:
kubectl apply -f - <<EOF
apiVersion: controller.devfile.io/v1alpha1
kind: DevWorkspaceOperatorConfig
metadata:
  name: devworkspace-operator-config
  namespace: openshift-operators
config:
  workspace:
    backupCronJob:
      enable: true
      schedule: "* * * * *"
      registry:
        path: "quay.io/rokumar"
        authSecret: "quay-push-secret"
EOF
  1. Create test namespace
oc new-project test-workspace-user-secret
  1. IMPORTANT: User manually creates secret BEFORE workspace (with different credentials):
kubectl create secret docker-registry devworkspace-backup-registry-auth \
  --docker-server=quay.io \
  --docker-username=user-scoped \
  --docker-password=user-token \
  -n test-workspace-user-secret

kubectl label secret devworkspace-backup-registry-auth \
  controller.devfile.io/watch-secret=true \
  -n test-workspace-user-secret

# Note the resourceVersion
echo "Original resourceVersion:"
kubectl get secret devworkspace-backup-registry-auth -n test-workspace-user-secret -o jsonpath='{.metadata.resourceVersion}'
echo
  1. Create a DevWorkspace
kubectl apply -f - <<EOF
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: test-workspace-user-secret
  namespace: test-workspace-user-secret
spec:
  started: true
  template:
    projects:
      - name: web-nodejs-sample
        git:
          remotes:
            origin: "https://github.com/che-samples/web-nodejs-sample.git"
    components:
      - name: dev
        container:
          image: quay.io/devfile/universal-developer-image:latest
EOF
  1. Wait for workspace to become running, then stop it:
kubectl patch devworkspace test-workspace-user-secret -n test-workspace-user-secret \
  --type merge -p '{"spec":{"started":false}}'
  1. Critical Test: Verify secret was NOT overwritten:
echo "Current resourceVersion:"
kubectl get secret devworkspace-backup-registry-auth -n test-workspace-user-secret -o jsonpath='{.metadata.resourceVersion}'
echo

# ResourceVersion should be EXACTLY THE SAME as step 4
Scenario 4: AuthSecret Configured but Operator Secret Missing

Expected: Operator logs error that it cannot find the configured secret in operator namespace.

  1. Configure DWOC with non-existent secret:
kubectl apply -f - <<EOF
apiVersion: controller.devfile.io/v1alpha1
kind: DevWorkspaceOperatorConfig
metadata:
  name: devworkspace-operator-config
  namespace: openshift-operators
config:
  workspace:
    backupCronJob:
      enable: true
      schedule: "* * * * *"
      registry:
        path: "quay.io/rokumar"
        authSecret: "missing-secret"  # This secret doesn't exist
EOF
  1. Create test namespace
oc new-project test-workspace-missing-secret
  1. Ensure no operator secret exists:
kubectl delete secret missing-secret -n openshift-operators --ignore-not-found
  1. Create a DevWorkspace
kubectl apply -f - <<EOF
apiVersion: workspace.devfile.io/v1alpha2
kind: DevWorkspace
metadata:
  name: test-workspace-missing
  namespace: test-workspace-missing-secret
spec:
  started: true
  template:
    projects:
      - name: web-nodejs-sample
        git:
          remotes:
            origin: "https://github.com/che-samples/web-nodejs-sample.git"
    components:
      - name: dev
        container:
          image: quay.io/devfile/universal-developer-image:latest
EOF
  1. Wait for workspace to become running, then stop it:
kubectl patch devworkspace test-workspace-missing -n test-workspace-missing-secret \
  --type merge -p '{"spec":{"started":false}}'
  1. Expected Result: Check operator logs for error:
oc logs -l app.kubernetes.io/name=devworkspace-controller -n openshift-operators -f

Expected log:

{"level":"error","ts":"2026-05-08T14:28:00Z","logger":"controllers.BackupCronJob","msg":"Failed to get registry auth secret for backup job","secretName":"missing-secret","namespace":"openshift-operators","error":"Secret \"missing-secret\" not found","stacktrace":"github.com/devfile/devworkspace-operator/pkg/secrets.HandleRegistryAuthSecret\n\t/devworkspace-operator/pkg/secrets/backup.go:88\ngithub.com/devfile/devworkspace-operator/controllers/backupcronjob.(*BackupCronJobReconciler).createBackupJob\n\t/devworkspace-operator/controllers/backupcronjob/backupcronjob_controller.go:348\ngithub.com/devfile/devworkspace-operator/controllers/backupcronjob.(*BackupCronJobReconciler).executeBackupSync\n\t/devworkspace-operator/controllers/backupcronjob/backupcronjob_controller.go:253\ngithub.com/devfile/devworkspace-operator/controllers/backupcronjob.(*BackupCronJobReconciler).startCron.func1\n\t/devworkspace-operator/controllers/backupcronjob/backupcronjob_controller.go:194\ngithub.com/robfig/cron/v3.FuncJob.Run\n\t/devworkspace-operator/vendor/github.com/robfig/cron/v3/cron.go:131\ngithub.com/robfig/cron/v3.(*Cron).startJob.func1\n\t/devworkspace-operator/vendor/github.com/robfig/cron/v3/cron.go:307"}
  1. Verify no secret was created in workspace namespace:
kubectl get secret devworkspace-backup-registry-auth -n test-workspace-missing-secret
# Expected: Error from server (NotFound)

PR Checklist

  • E2E tests pass (when PR is ready, comment /test v8-devworkspace-operator-e2e, v8-che-happy-path to trigger)
    • v8-devworkspace-operator-e2e: DevWorkspace e2e test
    • v8-che-happy-path: Happy path for verification integration with Che

Summary by CodeRabbit

  • Bug Fixes

    • Improved registry authentication secret handling to prevent unintended overwrites during backup operations.
    • Enhanced error messaging for misconfigured authentication secrets.
    • Added fallback logic for secret retrieval across workspace and operator environments.
  • Tests

    • Added comprehensive test coverage for backup path scenarios, including secret preservation and error cases.

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 5, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors registry auth secret handling in the backup path. HandleRegistryAuthSecret now uses a fixed constant name to look up secrets in the workspace namespace, returns an error if the secret is missing and no source is configured, and fetches from the operator namespace when configured. CopySecret is reimplemented with direct Kubernetes Create calls that enforce never-overwrite semantics by re-fetching existing secrets on AlreadyExists. Tests cover the backup-path scenarios with operator namespace fallback.

Changes

Registry Auth Secret Backup-Path Refactoring

Layer / File(s) Summary
Import cleanup
pkg/secrets/backup.go
Added Kubernetes API error helpers; removed sync package dependency.
Secret lookup and error handling
pkg/secrets/backup.go
HandleRegistryAuthSecret uses fixed constant name for workspace lookup, errors if unconfigured and missing, and fetches from operator namespace on fallback.
Create-based copy semantics
pkg/secrets/backup.go
CopySecret now creates directly with implicit existence check via AlreadyExists; re-fetches and returns concurrent secrets; removes sync-based reconciliation.
Backup-path test suite
pkg/secrets/backup_test.go
New Ginkgo suite for HandleRegistryAuthSecret backup path: existing secret preservation, unconfigured-missing error, operator-to-workspace copy, never-overwrite behavior, and missing operator secret error.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • devfile/devworkspace-operator#1614: Modifies the same HandleRegistryAuthSecret function and related tests; introduces restore-path fallback to predefined backup secret name alongside this PR's backup-path adjustments.

Suggested reviewers

  • ibuziuk

Poem

🐰 A secret path, now constant and clear,
No syncs to confuse, no overwrites here,
Create once, then fetch if it's there,
Backup and restore—a copy affair! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately describes the main change: refactoring backup secret handling to conditionally copy registry auth secrets from operator to workspace namespace based on configuration.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 5, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: rohanKanojia
Once this PR has been reviewed and has the lgtm label, please assign dkwon17 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@rohanKanojia rohanKanojia force-pushed the pr/secret-fallback-option branch from 56c6857 to f6f485c Compare May 5, 2026 11:13
@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

❌ Patch coverage is 64.51613% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.94%. Comparing base (b3e2af5) to head (f6f485c).
⚠️ Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
pkg/secrets/backup.go 69.81% 15 Missing and 1 partial ⚠️
apis/controller/v1alpha1/zz_generated.deepcopy.go 0.00% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1618      +/-   ##
==========================================
+ Coverage   35.48%   36.94%   +1.46%     
==========================================
  Files         168      168              
  Lines       14484    14726     +242     
==========================================
+ Hits         5139     5441     +302     
+ Misses       9006     8932      -74     
- Partials      339      353      +14     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rohanKanojia rohanKanojia force-pushed the pr/secret-fallback-option branch 3 times, most recently from 802c09c to 87f720c Compare May 5, 2026 15:16
@rohanKanojia rohanKanojia marked this pull request as ready for review May 5, 2026 15:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
controllers/backupcronjob/backupcronjob_controller_test.go (1)

312-314: ⚡ Quick win

Add one unset/false-path test for CopyOperatorAuthSecret.

All touched executeBackupSync fixtures now force CopyOperatorAuthSecret=true, so this suite no longer validates the new default/disabled branch. Please add at least one case for unset/false (especially missing workspace secret) to guard the new behavior.

Also applies to: 345-346, 403-404, 440-441, 482-483, 514-516, 551-553, 588-589

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@controllers/backupcronjob/backupcronjob_controller_test.go` around lines 312
- 314, Tests in backupcronjob_controller_test.go currently set
CopyOperatorAuthSecret = pointer.Bool(true) in all executeBackupSync fixtures,
so the default/disabled branch (unset/false) and the missing workspace secret
path are not covered; add at least one test case for executeBackupSync where
CopyOperatorAuthSecret is either nil (unset) or pointer.Bool(false) and the
workspace secret is absent, then assert the controller follows the disabled
behavior (e.g., does not attempt to copy operator auth and returns the expected
status/condition). Update the relevant table/fixtures used by
TestExecuteBackupSync (or similarly named test harness) to include this
false/unset case and verify expected outcomes for missing workspace secret to
guard the new branch.
deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml (1)

222-240: ⚡ Quick win

Set an explicit CRD default for copyOperatorAuthSecret.

Line 239 says the field defaults to false, but the schema currently has no default: false. Adding it makes the API contract enforceable and avoids nil/merge ambiguity when the field is omitted.

Suggested schema tweak
                           copyOperatorAuthSecret:
+                            default: false
                             description: |-
                               CopyOperatorAuthSecret controls whether the operator should copy the authentication
                               secret from the operator namespace to the workspace namespace when it's not found
                               in the workspace namespace.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml`
around lines 222 - 240, The CRD schema for the boolean field
copyOperatorAuthSecret currently documents "Defaults to false" but lacks an
explicit default; update the OpenAPI schema for the field copyOperatorAuthSecret
(the property under the spec schema) to include default: false so the CRD
enforces the default value when the field is omitted, ensuring API
server/merging behavior matches the documented contract.
pkg/secrets/backup.go (2)

101-176: 💤 Low value

Logic and race handling look correct.

EnsureRegistryAuthSecret correctly: (1) never overwrites a user-provided secret, (2) returns a clear, actionable error when copyOperatorAuthSecret is false, (3) handles the Get→Create race via IsAlreadyExists re-fetch, and (4) sets the watch label and controller reference on the created secret.

Two minor observations (no action required, just flagging for awareness):

  • The relevant code snippet from pkg/provision/sync/sync.go shows SyncObjectWithCluster provides diff/update behavior. Bypassing it here is the right call since the explicit goal is to never overwrite — just make sure no future caller expects this function to reconcile drift in the workspace secret (e.g., if the operator's source secret rotates, the workspace copy will not be updated).
  • The error message at lines 125–134 references DevWorkspaceBackupAuthSecretName (the predefined name) for both the "not found" name and the "please create" name. That's consistent with the rest of the function but means the user-facing message will not mention the configured secretName from Registry.AuthSecret, which can be confusing if the admin configured a custom name. Consider including both names in the error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` around lines 101 - 176, Update the error returned when
copyOperatorAuthSecret is false in EnsureRegistryAuthSecret to include both the
workspace secret name (constants.DevWorkspaceBackupAuthSecretName) and the
configured/operator secret name (use sourceSecret.Name) so users see which
configured auth secret name the operator expects versus the name missing in the
workspace; change the fmt.Errorf in the !copyOperatorAuthSecret branch inside
EnsureRegistryAuthSecret to format and include sourceSecret.Name alongside
constants.DevWorkspaceBackupAuthSecretName.

30-30: ⚡ Quick win

Replace deprecated k8s.io/utils/pointer with k8s.io/utils/ptr.

golangci-lint (SA1019) reports that k8s.io/utils/pointer is deprecated. Use ptr.Deref instead of pointer.BoolDeref.

♻️ Proposed fix
-	"k8s.io/utils/pointer"
+	"k8s.io/utils/ptr"
-	copyOperatorAuthSecret := pointer.BoolDeref(
-		dwOperatorConfig.Workspace.BackupCronJob.Registry.CopyOperatorAuthSecret,
-		false,
-	)
+	copyOperatorAuthSecret := ptr.Deref(
+		dwOperatorConfig.Workspace.BackupCronJob.Registry.CopyOperatorAuthSecret,
+		false,
+	)
k8s.io/utils/ptr Deref function signature
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` at line 30, Replace the deprecated import and calls to
pointer.*: change the import "k8s.io/utils/pointer" to "k8s.io/utils/ptr" and
update call sites using pointer.BoolDeref (and any other pointer.XDeref) to use
ptr.Deref instead, e.g. replace pointer.BoolDeref(someBoolPtr, false) with
ptr.Deref(someBoolPtr, false) in the functions/methods in pkg/secrets/backup.go.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml`:
- Around line 226-244: The CRD documents that copyOperatorAuthSecret defaults to
false but the schema lacks an enforced default; add a JSON schema default by
setting default: false for the copyOperatorAuthSecret property in the CRD (the
YAML under copyOperatorAuthSecret) so the API server will apply the default. If
the CRD is generated from Go types, add the kubebuilder default marker (e.g., //
+kubebuilder:default=false) to the Go field named CopyOperatorAuthSecret (or
copyOperatorAuthSecret) in the API type, regenerate the CRD manifests, and
verify the generated CRD includes default: false under the
copyOperatorAuthSecret schema.

In `@deploy/deployment/openshift/combined.yaml`:
- Around line 226-244: The CRD OpenAPI schema for the copyOperatorAuthSecret
boolean lacks an explicit default; add "default: false" to the property
definition in the combined.yaml CRD schema so the OpenAPIv3 spec matches the
documented and Go behavior (pointer.BoolDeref(..., false)); also update any CRD
description if needed to note this change is a breaking behavioral change for
users who relied on true.

In
`@deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml`:
- Around line 223-224: The sentence about the
"controller.devfile.io/watch-secret=true" label is ambiguous: update the text
that references authSecret to state this label requirement only applies to
operator-managed (copied) secrets when copyOperatorAuthSecret is enabled (i.e.,
the operator-created/copy-source path), and clarify that user-provided/manual
authSecret values do not need that label; reference the fields authSecret and
copyOperatorAuthSecret and explicitly mention "operator-managed/copy-source
secrets" and "user-provided/manual secrets" so readers know which secrets must
have controller.devfile.io/watch-secret=true.

In
`@deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml`:
- Around line 224-242: The CRD schema for the boolean property
copyOperatorAuthSecret declares "Defaults to false" in the description but lacks
an OpenAPI default; update the CRD schema for the copyOperatorAuthSecret
property by adding default: false alongside its type: boolean (i.e., under the
copyOperatorAuthSecret property in the CRD's schema) so the generated API docs
and schema reflect the described default behavior.

In `@pkg/secrets/backup.go`:
- Around line 57-61: The change introduced a breaking default (currently using
pointer.BoolDeref(..., false)) for
dwOperatorConfig.Workspace.BackupCronJob.Registry.CopyOperatorAuthSecret which
stops auto-copying the operator namespace secret; restore backward-compatible
behavior by changing the default to true in the copyOperatorAuthSecret
initialization (i.e., use pointer.BoolDeref(..., true)) so existing clusters
continue to auto-copy, and if you intentionally want false instead, update
docs/changelog and tests that rely on auto-copy (e.g.,
controllers/backupcronjob/backupcronjob_controller_test.go) to avoid upgrade
surprises.

---

Nitpick comments:
In `@controllers/backupcronjob/backupcronjob_controller_test.go`:
- Around line 312-314: Tests in backupcronjob_controller_test.go currently set
CopyOperatorAuthSecret = pointer.Bool(true) in all executeBackupSync fixtures,
so the default/disabled branch (unset/false) and the missing workspace secret
path are not covered; add at least one test case for executeBackupSync where
CopyOperatorAuthSecret is either nil (unset) or pointer.Bool(false) and the
workspace secret is absent, then assert the controller follows the disabled
behavior (e.g., does not attempt to copy operator auth and returns the expected
status/condition). Update the relevant table/fixtures used by
TestExecuteBackupSync (or similarly named test harness) to include this
false/unset case and verify expected outcomes for missing workspace secret to
guard the new branch.

In
`@deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml`:
- Around line 222-240: The CRD schema for the boolean field
copyOperatorAuthSecret currently documents "Defaults to false" but lacks an
explicit default; update the OpenAPI schema for the field copyOperatorAuthSecret
(the property under the spec schema) to include default: false so the CRD
enforces the default value when the field is omitted, ensuring API
server/merging behavior matches the documented contract.

In `@pkg/secrets/backup.go`:
- Around line 101-176: Update the error returned when copyOperatorAuthSecret is
false in EnsureRegistryAuthSecret to include both the workspace secret name
(constants.DevWorkspaceBackupAuthSecretName) and the configured/operator secret
name (use sourceSecret.Name) so users see which configured auth secret name the
operator expects versus the name missing in the workspace; change the fmt.Errorf
in the !copyOperatorAuthSecret branch inside EnsureRegistryAuthSecret to format
and include sourceSecret.Name alongside
constants.DevWorkspaceBackupAuthSecretName.
- Line 30: Replace the deprecated import and calls to pointer.*: change the
import "k8s.io/utils/pointer" to "k8s.io/utils/ptr" and update call sites using
pointer.BoolDeref (and any other pointer.XDeref) to use ptr.Deref instead, e.g.
replace pointer.BoolDeref(someBoolPtr, false) with ptr.Deref(someBoolPtr, false)
in the functions/methods in pkg/secrets/backup.go.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58f3b582-37ca-446d-9ace-6bf37dec79a5

📥 Commits

Reviewing files that changed from the base of the PR and between fa371eb and 87f720c.

📒 Files selected for processing (12)
  • apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
  • apis/controller/v1alpha1/zz_generated.deepcopy.go
  • controllers/backupcronjob/backupcronjob_controller_test.go
  • deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • deploy/deployment/kubernetes/combined.yaml
  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/deployment/openshift/combined.yaml
  • deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • pkg/config/sync.go
  • pkg/secrets/backup.go
  • pkg/secrets/backup_test.go

Comment thread deploy/deployment/openshift/combined.yaml Outdated
Comment on lines 223 to 224
The secret must contain "controller.devfile.io/watch-secret=true" label so that it can be
recognized by the operator.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify label requirement scope for user-provided secrets

Line 223 currently reads as if all authSecret values must have controller.devfile.io/watch-secret=true. That conflicts with the manual-secret path when copyOperatorAuthSecret is disabled. Please scope this sentence to operator-managed/copy-source secrets only.

✏️ Proposed wording tweak
- The secret must contain "controller.devfile.io/watch-secret=true" label so that it can be
- recognized by the operator.
+ For operator-managed secret copying, the source secret in the operator namespace must contain
+ "controller.devfile.io/watch-secret=true" so the operator can recognize and manage it.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The secret must contain "controller.devfile.io/watch-secret=true" label so that it can be
recognized by the operator.
For operator-managed secret copying, the source secret in the operator namespace must contain
"controller.devfile.io/watch-secret=true" so the operator can recognize and manage it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml`
around lines 223 - 224, The sentence about the
"controller.devfile.io/watch-secret=true" label is ambiguous: update the text
that references authSecret to state this label requirement only applies to
operator-managed (copied) secrets when copyOperatorAuthSecret is enabled (i.e.,
the operator-created/copy-source path), and clarify that user-provided/manual
authSecret values do not need that label; reference the fields authSecret and
copyOperatorAuthSecret and explicitly mention "operator-managed/copy-source
secrets" and "user-provided/manual secrets" so readers know which secrets must
have controller.devfile.io/watch-secret=true.

Comment thread deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml Outdated
Comment thread pkg/secrets/backup.go Outdated
@rohanKanojia rohanKanojia force-pushed the pr/secret-fallback-option branch from 87f720c to 0f8ddda Compare May 5, 2026 16:47
Comment on lines +93 to +95
// If true: The operator will copy the secret from the operator namespace
// if it's not found in the workspace namespace. This provides automatic configuration
// but exposes operator-level credentials to workspace users.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a bit verbose,

Can we update thee If true... section to:

If true, this enables the fallback mechanism where the operator will copy the secret from the operator namespace.

//
// If false (default): The operator will not copy the secret. Users must manually create a secret
// with the configured name in their workspace namespace. This is more secure as it allows
// users to provide scoped credentials with minimal privileges.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a bit verbose,

Can we update the If false... section to:

If false, the operator will not copy the secret to workspace namespaces. 

// users to provide scoped credentials with minimal privileges.
//
// Note: Regardless of this setting, if a secret already exists in the workspace namespace,
// it will never be overwritten. User-provided secrets are always respected.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// it will never be overwritten. User-provided secrets are always respected.
// it will never be overwritten.

Comment thread pkg/secrets/backup.go Outdated
// Extract flag value (default: false, users must provide their own secret)
copyOperatorAuthSecret := pointer.BoolDeref(
dwOperatorConfig.Workspace.BackupCronJob.Registry.CopyOperatorAuthSecret,
false,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we instead define the default field here?

pkg/config/defaults.go

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, I also thought about it, but then hesitated as RegistryConfig wasn't defined in defaults.go as Registry.Path is a required value with no sensible default.

If we add configuration like this for the default value:

  BackupCronJob: &v1alpha1.BackupCronJobConfig{
      Enable:       pointer.Bool(false),
      Schedule:     "0 0 1 * *",
      BackoffLimit: pointer.Int32(1),
      Registry: &v1alpha1.RegistryConfig{
          Path:                   "", // Required but no default
          AuthSecret:             "",
          CopyOperatorAuthSecret: pointer.Bool(false),
      },
  },

But this would generate an incomplete/invalid configuration.

Do you think I should proceed with this approach? Or create a named constant for false value in the current line?

Comment thread pkg/secrets/backup.go Outdated
return nil, fmt.Errorf(
"registry auth secret %q not found in workspace namespace %q. "+
"copyOperatorAuthSecret is set to false, so the operator will not copy the secret. "+
"Please manually create a secret of type kubernetes.io/dockerconfigjson with the name %q "+
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Please manually create a secret of type kubernetes.io/dockerconfigjson with the name %q "+
"Please create a secret of type kubernetes.io/dockerconfigjson with the name %q "+

Comment thread pkg/secrets/backup.go Outdated
if !copyOperatorAuthSecret {
return nil, fmt.Errorf(
"registry auth secret %q not found in workspace namespace %q. "+
"copyOperatorAuthSecret is set to false, so the operator will not copy the secret. "+
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"copyOperatorAuthSecret is set to false, so the operator will not copy the secret. "+
"copyOperatorAuthSecret is set to false, the secret will not be copied. "+

@dkwon17
Copy link
Copy Markdown
Collaborator

dkwon17 commented May 6, 2026

@rohanKanojia on second thought, is there ever a use case where the admin sets an authSecret and sets copyOperatorAuthSecret to false?

config:
  workspace:
    backupCronJob:
      enable: true
      schedule: "* * * * *"
      registry:
        path: "quay.io/rokumar"
        authSecret: "quay-push-secret"
        copyOperatorAuthSecret: false

Because, the only purpose of the admin supplying the authSecret is so that it can be used in user namespace, but in the case above, copyOperatorAuthSecret will prevent that.

Also in general the secret name provided inauthSecret name is now not important, since when checking/retrieving the pull secret from the user namespace, the code always checks for a secret named: devworkspace-backup-registry-auth(value of constants.DevWorkspaceBackupAuthSecretName)

@rohanKanojia rohanKanojia force-pushed the pr/secret-fallback-option branch from 0f8ddda to 43f4862 Compare May 7, 2026 04:28
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/secrets/backup.go (1)

63-98: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

AuthSecret name is silently ignored on the workspace-namespace fallback path — confirm UX.

The function uses two different names depending on the path:

  • Backup path (line 66, lookupName = secretName): the workspace namespace is searched under the operator-configured AuthSecret name.
  • Restore path (line 68) and the EnsureRegistryAuthSecret write path (line 108): the predefined constants.DevWorkspaceBackupAuthSecretName is used.

So when an admin sets authSecret: my-secret and copyOperatorAuthSecret: false, and the workspace user has not pre-created any secret, the workspace lookup at line 71–79 will succeed only if they created my-secret, but EnsureRegistryAuthSecret (and the error it returns at lines 125–134) instructs them to create devworkspace-backup-registry-auth. The two paths disagree on the expected name.

Please align the names — either always look for/instruct on constants.DevWorkspaceBackupAuthSecretName, or always honor the operator-configured AuthSecret name in both lookup and the user-facing error. This was also raised in PR review feedback by dkwon17.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` around lines 63 - 98, The code currently looks up the
workspace secret using secretName but EnsureRegistryAuthSecret (and its
error/log messages) uses constants.DevWorkspaceBackupAuthSecretName, causing a
mismatch; fix by making the operator-configured AuthSecret name authoritative:
update EnsureRegistryAuthSecret (and any user-facing error/log text) to use the
passed-in secretName instead of constants.DevWorkspaceBackupAuthSecretName so
the lookupName logic, the Get calls, and the EnsureRegistryAuthSecret
write/error messaging all reference the same secretName; ensure references to
lookupName, secretName, and EnsureRegistryAuthSecret are consistent and adjust
tests/logs accordingly.
♻️ Duplicate comments (1)
deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml (1)

221-224: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Scope the watch-secret label requirement to operator-managed copy-source secrets only.

The label sentence is still ambiguous and reads as if it applies to all authSecret values. Please explicitly distinguish operator-managed/copy-source secrets vs user-provided/manual workspace secrets.

✏️ Suggested doc tweak
- If secret is not found in the workspace namespace and copyOperatorAuthSecret is true,
- the operator will copy the secret from the operator namespace to the workspace namespace.
- The secret must contain "controller.devfile.io/watch-secret=true" label so that it can be
- recognized by the operator.
+ If secret is not found in the workspace namespace and copyOperatorAuthSecret is true,
+ the operator will copy the secret from the operator namespace to the workspace namespace.
+ For operator-managed/copy-source secrets, the source secret in the operator namespace must
+ contain "controller.devfile.io/watch-secret=true" so it can be recognized by the operator.
+ User-provided/manual secrets in workspace namespaces do not require this label.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml`
around lines 221 - 224, Clarify that the
"controller.devfile.io/watch-secret=true" label is only required for
operator-managed copy-source secrets when copyOperatorAuthSecret is true: update
the description around the authSecret/copyOperatorAuthSecret text to state that
if copyOperatorAuthSecret is true the operator will copy the secret from the
operator namespace and that only those copy-source secrets must carry the
controller.devfile.io/watch-secret=true label; explicitly note that
user-provided/manual workspace secrets (i.e., when users set authSecret directly
in the workspace namespace) do not need this label. Reference
copyOperatorAuthSecret and authSecret and the watch-secret label in the updated
sentence so it's unambiguous.
🧹 Nitpick comments (2)
pkg/secrets/backup.go (2)

87-87: 💤 Low value

Log key/value mismatch on the restore path.

lookupName was used for the workspace-namespace Get (line 73), but this log line reports secretName as the secret it failed to find in the workspace namespace. On the restore path these can differ (line 68 overrides lookupName to the predefined constant), so the log will misattribute the missing key. Use lookupName here.

♻️ Proposed fix
-	log.Info("Registry auth secret not found in workspace namespace, checking operator namespace", "secretName", secretName)
+	log.Info("Registry auth secret not found in workspace namespace, checking operator namespace",
+		"workspaceSecretName", lookupName, "operatorSecretName", secretName)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` at line 87, The log message in pkg/secrets/backup.go
incorrectly reports "secretName" when the code uses lookupName for the
workspace-namespace Get; update the log.Info call that currently logs
"secretName" to instead log the lookupName variable so the message reflects the
actual attempted key (refer to the lookupName variable and the log.Info call
that says "Registry auth secret not found in workspace namespace, checking
operator namespace").

27-30: 💤 Low value

Switch from deprecated k8s.io/utils/pointer to k8s.io/utils/ptr.

The pointer package is deprecated in favor of ptr. Use ptr.Deref (replacing pointer.BoolDeref) to align with Kubernetes project migration guidance.

♻️ Proposed refactor
-	"k8s.io/utils/pointer"
+	"k8s.io/utils/ptr"
-	copyOperatorAuthSecret := pointer.BoolDeref(
-		dwOperatorConfig.Workspace.BackupCronJob.Registry.CopyOperatorAuthSecret,
-		false,
-	)
+	copyOperatorAuthSecret := ptr.Deref(
+		dwOperatorConfig.Workspace.BackupCronJob.Registry.CopyOperatorAuthSecret,
+		false,
+	)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` around lines 27 - 30, The code imports the deprecated
package alias pointer and uses pointer.BoolDeref; replace the import
"k8s.io/utils/pointer" with "k8s.io/utils/ptr" (alias it as ptr if you used
pointer) and update usages of pointer.BoolDeref to ptr.Deref (e.g.,
pointer.BoolDeref(someBoolPtr, false) -> ptr.Deref(someBoolPtr, false)); also
change any other pointer.* helpers to their ptr.* equivalents (e.g.,
pointer.Bool -> ptr.Bool) so the file uses the non-deprecated ptr package and
ptr.Deref consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/secrets/backup.go`:
- Around line 125-134: The user-facing error string in the fmt.Errorf return
(the block referencing constants.DevWorkspaceBackupAuthSecretName and
workspace.Namespace in the copyOperatorAuthSecret branch) concatenates sentences
without a space resulting in "...will not be copied.Please create..."; fix it by
inserting a space between the period and "Please" (or restructure the string
concatenation so sentences are separated with a space) so the message reads
"...will not be copied. Please create...".

---

Outside diff comments:
In `@pkg/secrets/backup.go`:
- Around line 63-98: The code currently looks up the workspace secret using
secretName but EnsureRegistryAuthSecret (and its error/log messages) uses
constants.DevWorkspaceBackupAuthSecretName, causing a mismatch; fix by making
the operator-configured AuthSecret name authoritative: update
EnsureRegistryAuthSecret (and any user-facing error/log text) to use the
passed-in secretName instead of constants.DevWorkspaceBackupAuthSecretName so
the lookupName logic, the Get calls, and the EnsureRegistryAuthSecret
write/error messaging all reference the same secretName; ensure references to
lookupName, secretName, and EnsureRegistryAuthSecret are consistent and adjust
tests/logs accordingly.

---

Duplicate comments:
In
`@deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml`:
- Around line 221-224: Clarify that the
"controller.devfile.io/watch-secret=true" label is only required for
operator-managed copy-source secrets when copyOperatorAuthSecret is true: update
the description around the authSecret/copyOperatorAuthSecret text to state that
if copyOperatorAuthSecret is true the operator will copy the secret from the
operator namespace and that only those copy-source secrets must carry the
controller.devfile.io/watch-secret=true label; explicitly note that
user-provided/manual workspace secrets (i.e., when users set authSecret directly
in the workspace namespace) do not need this label. Reference
copyOperatorAuthSecret and authSecret and the watch-secret label in the updated
sentence so it's unambiguous.

---

Nitpick comments:
In `@pkg/secrets/backup.go`:
- Line 87: The log message in pkg/secrets/backup.go incorrectly reports
"secretName" when the code uses lookupName for the workspace-namespace Get;
update the log.Info call that currently logs "secretName" to instead log the
lookupName variable so the message reflects the actual attempted key (refer to
the lookupName variable and the log.Info call that says "Registry auth secret
not found in workspace namespace, checking operator namespace").
- Around line 27-30: The code imports the deprecated package alias pointer and
uses pointer.BoolDeref; replace the import "k8s.io/utils/pointer" with
"k8s.io/utils/ptr" (alias it as ptr if you used pointer) and update usages of
pointer.BoolDeref to ptr.Deref (e.g., pointer.BoolDeref(someBoolPtr, false) ->
ptr.Deref(someBoolPtr, false)); also change any other pointer.* helpers to their
ptr.* equivalents (e.g., pointer.Bool -> ptr.Bool) so the file uses the
non-deprecated ptr package and ptr.Deref consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a377f982-ce2d-4827-926d-c2adb24e358e

📥 Commits

Reviewing files that changed from the base of the PR and between 87f720c and 43f4862.

📒 Files selected for processing (12)
  • apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
  • apis/controller/v1alpha1/zz_generated.deepcopy.go
  • controllers/backupcronjob/backupcronjob_controller_test.go
  • deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • deploy/deployment/kubernetes/combined.yaml
  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/deployment/openshift/combined.yaml
  • deploy/deployment/openshift/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • pkg/config/sync.go
  • pkg/secrets/backup.go
  • pkg/secrets/backup_test.go
✅ Files skipped from review due to trivial changes (1)
  • deploy/deployment/kubernetes/combined.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
  • pkg/config/sync.go
  • apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
  • deploy/deployment/kubernetes/objects/devworkspaceoperatorconfigs.controller.devfile.io.CustomResourceDefinition.yaml
  • deploy/deployment/openshift/combined.yaml
  • deploy/templates/crd/bases/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • pkg/secrets/backup_test.go
  • deploy/bundle/manifests/controller.devfile.io_devworkspaceoperatorconfigs.yaml
  • apis/controller/v1alpha1/zz_generated.deepcopy.go

Comment thread pkg/secrets/backup.go Outdated
@rohanKanojia rohanKanojia force-pushed the pr/secret-fallback-option branch from 18bf441 to 385a1b3 Compare May 7, 2026 09:40
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented May 7, 2026

@rohanKanojia: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/v14-devworkspace-operator-e2e 385a1b3 link true /test v14-devworkspace-operator-e2e

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@rohanKanojia rohanKanojia force-pushed the pr/secret-fallback-option branch from 385a1b3 to 8afc9dc Compare May 8, 2026 11:33
@rohanKanojia rohanKanojia requested a review from btjd as a code owner May 8, 2026 11:33
@rohanKanojia rohanKanojia marked this pull request as draft May 8, 2026 11:34
@rohanKanojia rohanKanojia changed the title feat (backup) : add option to conditionally copy registry auth secret from operator namespace to workspace namespace for backup/restore refactor (backup) : conditionally copy registry auth secret from operator namespace to workspace namespace for backup/restore May 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
pkg/secrets/backup.go (2)

34-40: ⚡ Quick win

Stale doc comment references the old function name.

The godoc on line 34 still says GetRegistryAuthSecret while the function is now GetNamespaceRegistryAuthSecret. Please align the comment with the new name (and ideally describe what "Namespace" means here since this thin wrapper just calls HandleRegistryAuthSecret with an empty operatorConfigNamespace).

📝 Suggested doc fix
-// GetRegistryAuthSecret retrieves the registry authentication secret for accessing backup images
-// based on the operator configuration.
+// GetNamespaceRegistryAuthSecret retrieves the registry authentication secret from the workspace
+// namespace only (no fallback to the operator namespace). It is intended for the restore path where
+// only user-provided, workspace-scoped credentials should be used.
 func GetNamespaceRegistryAuthSecret(ctx context.Context, c client.Client, workspace *dw.DevWorkspace,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` around lines 34 - 40, Update the stale godoc for
GetNamespaceRegistryAuthSecret to reference its current name (replace
"GetRegistryAuthSecret" with "GetNamespaceRegistryAuthSecret") and clarify that
this wrapper calls HandleRegistryAuthSecret with an empty
operatorConfigNamespace to use the default/operator-scoped config; mention what
"Namespace" refers to (i.e., it operates for the workspace namespace by passing
an empty operatorConfigNamespace). Ensure the comment sits immediately above the
GetNamespaceRegistryAuthSecret function signature.

94-152: ⚡ Quick win

CopySecret is now narrowly purpose-built for the backup-auth secret; consider renaming/narrowing the signature.

The function still has the generic name CopySecret(... sourceSecret *corev1.Secret ...), but it ignores sourceSecret.Name/sourceSecret.Namespace and hardcodes the destination name to constants.DevWorkspaceBackupAuthSecretName. A future caller passing sourceSecret named "foo" would silently end up with a workspace secret named devworkspace-backup-registry-auth, which is surprising.

Two reasonable options:

  1. Rename to something like EnsureBackupAuthSecretInWorkspace and shrink the signature to just the bits actually consumed (data map[string][]byte, secretType corev1.SecretType), making it explicit that this is not a general-purpose copy.
  2. Keep the generic name but actually honor sourceSecret.Name (and require it to equal the constant when called from HandleRegistryAuthSecret), so the function stays reusable.

Also note: this nicely pairs with the previous comment — if Registry.AuthSecret becomes the operator-namespace source name, option (1) becomes the cleaner separation, since the source/destination naming differ.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` around lines 94 - 152, The CopySecret function is
actually specialized for the backup-auth secret but still takes a generic
sourceSecret and ignores its name; rename CopySecret to
EnsureBackupAuthSecretInWorkspace (or similar) and shrink its signature to only
accept the fields it uses (e.g., data map[string][]byte, secretType
corev1.SecretType, workspace *dw.DevWorkspace, scheme *runtime.Scheme,
ctx/client/log) so callers can't accidentally expect a generic copy; update all
callers (e.g., HandleRegistryAuthSecret) to pass sourceSecret.Data and
sourceSecret.Type (or keep a thin adapter if needed), keep the hardcoded
destination name constants.DevWorkspaceBackupAuthSecretName and the existing
controllerutil.SetControllerReference, and preserve the existing
Get/Create/IsAlreadyExists race handling and logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/secrets/backup.go`:
- Around line 67-89: The code currently ignores the admin-configured name in
dwOperatorConfig.Workspace.BackupCronJob.Registry.AuthSecret and always looks up
constants.DevWorkspaceBackupAuthSecretName in the operator namespace; change the
operator-namespace lookup (the c.Get call that populates registryAuthSecret) to
use the configured name
(dwOperatorConfig.Workspace.BackupCronJob.Registry.AuthSecret) instead of the
constant, and update the preceding error/log message to reflect that the
configured name will be used for the operator-namespace lookup; also update
related tests (backup_test.go) to seed the operator namespace with the
configured secret name rather than constants.DevWorkspaceBackupAuthSecretName.

---

Nitpick comments:
In `@pkg/secrets/backup.go`:
- Around line 34-40: Update the stale godoc for GetNamespaceRegistryAuthSecret
to reference its current name (replace "GetRegistryAuthSecret" with
"GetNamespaceRegistryAuthSecret") and clarify that this wrapper calls
HandleRegistryAuthSecret with an empty operatorConfigNamespace to use the
default/operator-scoped config; mention what "Namespace" refers to (i.e., it
operates for the workspace namespace by passing an empty
operatorConfigNamespace). Ensure the comment sits immediately above the
GetNamespaceRegistryAuthSecret function signature.
- Around line 94-152: The CopySecret function is actually specialized for the
backup-auth secret but still takes a generic sourceSecret and ignores its name;
rename CopySecret to EnsureBackupAuthSecretInWorkspace (or similar) and shrink
its signature to only accept the fields it uses (e.g., data map[string][]byte,
secretType corev1.SecretType, workspace *dw.DevWorkspace, scheme
*runtime.Scheme, ctx/client/log) so callers can't accidentally expect a generic
copy; update all callers (e.g., HandleRegistryAuthSecret) to pass
sourceSecret.Data and sourceSecret.Type (or keep a thin adapter if needed), keep
the hardcoded destination name constants.DevWorkspaceBackupAuthSecretName and
the existing controllerutil.SetControllerReference, and preserve the existing
Get/Create/IsAlreadyExists race handling and logging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 48f686d6-3831-4b20-8a0e-357bc50c82c8

📥 Commits

Reviewing files that changed from the base of the PR and between 385a1b3 and 8afc9dc.

📒 Files selected for processing (2)
  • pkg/secrets/backup.go
  • pkg/secrets/backup_test.go

Comment thread pkg/secrets/backup.go
Comment on lines +67 to 89

// Check if AuthSecret is configured in operator config
if len(dwOperatorConfig.Workspace.BackupCronJob.Registry.AuthSecret) == 0 {
return nil, fmt.Errorf(
"registry auth secret %q not found in workspace namespace %q and AuthSecret is not configured in DevWorkspaceOperatorConfig. "+
"Either create the secret in the workspace namespace or configure Registry.AuthSecret in the operator config to enable copying from the operator namespace",
constants.DevWorkspaceBackupAuthSecretName,
workspace.Namespace,
)
}

log.Info("Registry auth secret not found in workspace namespace, checking operator namespace",
"secretName", constants.DevWorkspaceBackupAuthSecretName,
"operatorNamespace", operatorConfigNamespace)

// If the secret is not found in the workspace namespace, check the operator namespace as fallback
err = c.Get(ctx, client.ObjectKey{
Name: secretName,
Name: constants.DevWorkspaceBackupAuthSecretName,
Namespace: operatorConfigNamespace}, registryAuthSecret)
if err != nil {
log.Error(err, "Failed to get registry auth secret for backup job", "secretName", secretName)
log.Error(err, "Failed to get registry auth secret for backup job", "secretName", constants.DevWorkspaceBackupAuthSecretName)
return nil, err
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Admin-configured Registry.AuthSecret name is silently ignored.

The gate at line 69 only checks whether Registry.AuthSecret is non-empty, but the actual operator-namespace lookup at line 84 uses the hard-coded constants.DevWorkspaceBackupAuthSecretName. Net effect: an admin who configures AuthSecret: "my-org-pull-secret" will hit a confusing NotFound for devworkspace-backup-registry-auth in the operator namespace, with no hint that the configured name was disregarded. This matches the concern raised on the PR that the configured AuthSecret name is effectively ignored by the lookup logic.

There are also two contradictory readings of the same field in this code path:

  • The error string at line 71-72 tells admins to "configure Registry.AuthSecret in the operator config to enable copying", treating it as a boolean.
  • The CRD/type defines AuthSecret as a name (string), so admins reasonably expect it to be the name used for the lookup.

Pick one model and stick to it:

  1. Treat AuthSecret as a name (preferred, matches the field's existing semantics): use it for the operator-namespace Get, and only fall through if it is set.
  2. Use a dedicated boolean (copyOperatorAuthSecret mentioned in the PR description) for the toggle, and keep AuthSecret as the source name in the operator namespace.

Either way, the configured value should not be silently dropped.

🛠️ Sketch for option 1 (use AuthSecret as the operator-namespace name)
-	// Check if AuthSecret is configured in operator config
-	if len(dwOperatorConfig.Workspace.BackupCronJob.Registry.AuthSecret) == 0 {
+	operatorAuthSecretName := dwOperatorConfig.Workspace.BackupCronJob.Registry.AuthSecret
+	if operatorAuthSecretName == "" {
 		return nil, fmt.Errorf(
 			"registry auth secret %q not found in workspace namespace %q and AuthSecret is not configured in DevWorkspaceOperatorConfig. "+
 				"Either create the secret in the workspace namespace or configure Registry.AuthSecret in the operator config to enable copying from the operator namespace",
 			constants.DevWorkspaceBackupAuthSecretName,
 			workspace.Namespace,
 		)
 	}

 	log.Info("Registry auth secret not found in workspace namespace, checking operator namespace",
-		"secretName", constants.DevWorkspaceBackupAuthSecretName,
+		"secretName", operatorAuthSecretName,
 		"operatorNamespace", operatorConfigNamespace)

 	// If the secret is not found in the workspace namespace, check the operator namespace as fallback
 	err = c.Get(ctx, client.ObjectKey{
-		Name:      constants.DevWorkspaceBackupAuthSecretName,
+		Name:      operatorAuthSecretName,
 		Namespace: operatorConfigNamespace}, registryAuthSecret)

The corresponding tests in backup_test.go (e.g. lines 193-217, 248-258) would need to seed/reference operatorAuthSecretName rather than constants.DevWorkspaceBackupAuthSecretName in the operator namespace.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/secrets/backup.go` around lines 67 - 89, The code currently ignores the
admin-configured name in
dwOperatorConfig.Workspace.BackupCronJob.Registry.AuthSecret and always looks up
constants.DevWorkspaceBackupAuthSecretName in the operator namespace; change the
operator-namespace lookup (the c.Get call that populates registryAuthSecret) to
use the configured name
(dwOperatorConfig.Workspace.BackupCronJob.Registry.AuthSecret) instead of the
constant, and update the preceding error/log message to reflect that the
configured name will be used for the operator-namespace lookup; also update
related tests (backup_test.go) to seed the operator namespace with the
configured secret name rather than constants.DevWorkspaceBackupAuthSecretName.

@tolusha
Copy link
Copy Markdown
Contributor

tolusha commented May 8, 2026

Hi! I'm che-ai-assistant — I help with your pull requests.

I check for new commands every 5m0s (if I am not busy :) ).

Available commands:

  • /che-ai-assistant generate-che-doc — Generate a documentation PR based on this PR's changes
  • /che-ai-assistant ok-pr-review — Run a comprehensive PR review (summary, code review, deep review, impact analysis)
  • /che-ai-assistant help — Show this help message

…ator namespace to workspace namespace for backup/restore

Only copy the registry auth secret from the operator namespace to the workspace namespace when it is configured inside DevWorkspaceOperatorConfig. Always give precedence to the user-defined secret named devworkspace-backup-registry-auth in the workspace namespace; do not modify it when it's present while copying.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rohan Kumar <rohaan@redhat.com>
@tolusha
Copy link
Copy Markdown
Contributor

tolusha commented May 8, 2026

/che-ai-assistant generate-che-doc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants